package v1

import (
	"archive/zip"
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"log/slog"
	"slices"
	"strings"
	"time"
	"unicode/utf8"

	"github.com/gofiber/websocket/v2"
	"github.com/google/cel-go/cel"
	"github.com/lithammer/shortuuid/v4"
	"github.com/pkg/errors"
	"github.com/usememos/gomark/ast"
	"github.com/usememos/gomark/parser"
	"github.com/usememos/gomark/parser/tokenizer"
	"github.com/usememos/gomark/renderer"
	"github.com/usememos/gomark/restore"

	expr "google.golang.org/genproto/googleapis/api/expr/v1alpha1"

	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/encoding/protojson"
	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/types/known/emptypb"
	"google.golang.org/protobuf/types/known/timestamppb"

	"quipus/internal/database/model"
	llm "quipus/internal/llm"
	"quipus/internal/server/process"

	"quipus/internal/utils"

	"quipus/pkg/webhook"
	v1pb "quipus/proto/gen/api/v1"
	apb "quipus/proto/gen/assist"
	cpb "quipus/proto/gen/common"
)

const (
	DefaultPageSize = 10
)

type CreateKnotOptions struct {
	Parent    int32
	Interact  string
	SubjectId int32
	TopicId   int32
	Subject   string
}

type ChatService struct {
	// ... existing fields ...
	// done      chan struct{}
	userId    int32
	subjectId int32
	subject   string
	api       *APIV1Service
	ctx       context.Context
	history   *[]llm.RoleContent
}

func (s *APIV1Service) CreateKnot(ctx context.Context, request *v1pb.CreateKnotRequest) (*v1pb.Knot, error) {
	subject, err := s.getKnotSubject(ctx, request.Subject, request.SubjectUrn)
	if err != nil {
		return nil, err
	}

	knot, err := s.createKnot(ctx, request, subject, nil)
	if err != nil {
		return nil, err
	}

	if len(request.Resources) > 0 {
		_, err := s.SetKnotResources(ctx, &v1pb.SetKnotResourcesRequest{
			Urn:       utils.BuildUrn(utils.KnotUrnPrefix, knot.ID),
			Resources: request.Resources,
		})
		if err != nil {
			return nil, errors.Wrap(err, "failed to set knot resources")
		}
	}

	knotMsg, err := s.convertKnotFromDB(ctx, knot)
	if err != nil {
		return nil, err
	}

	if request.InteractType == cpb.InteractType_CHAT {
		s.registerWebsocket(ctx, knot, subject, request.Content, nil)
	} else if request.InteractType == cpb.InteractType_QUERY {
		s.ProcessCompleteKnot(ctx, knot, subject, request.Content)
	}

	return knotMsg, nil
}

func (s *APIV1Service) getKnotSubject(ctx context.Context, matter string, urn string) (*model.Subject, error) {
	if urn != "" {
		subjectId, err := utils.ExtractIDFromUrn(utils.SubjectUrnPrefix, urn)
		if err != nil {
			return nil, err
		}
		subject, err := s.Gds.Handler.GetSubjectById(ctx, subjectId)
		if err == nil {
			return subject, err
		} else {
			slog.Error("Failed to get subject")
			return nil, err
		}

	} else if matter != "" {
		subject, err := s.Gds.Handler.GetSubject(ctx, matter)
		if err == nil {
			return subject, err
		} else {
			return nil, err
		}
	}

	return nil, nil
}

func (s *APIV1Service) ProcessCompleteKnot(ctx context.Context, srcKnot *model.Knot, subject *model.Subject, content string) {
	responders := []int32{}
	if srcKnot.Responders != "" {
		err := json.Unmarshal([]byte(srcKnot.Responders), &responders)
		if err != nil {
			slog.Error("Failed to unmarshal responders", slog.Any("error", err))
		}
	}

	if len(responders) == 0 {
		if subject.DefaultResponder == 0 {
			slog.Error("No responder found")
			return
		} else {
			responders = []int32{subject.DefaultResponder}
		}
	}

	if srcKnot.TopicID > 0 {
		topic, err := s.Gds.Handler.GetTopic(ctx, srcKnot.TopicID)
		if err != nil {
			return
		}
		if content == "" {
			content = topic.Title
		}
	}

	p := process.NewProcess(s.Gds)
	p.DataPath = s.Profile.Data
	wkp, err := p.NewWorkflowProcess(ctx, subject, responders)
	if err != nil {
		slog.Error("Failed to create workflow process", slog.Any("error", err))
		return
	}

	_, err = wkp.ProcessComplete(ctx, content, srcKnot)
	if err != nil {
		slog.Error("Failed to process query", slog.Any("error", err))
	}

	//save rsp
	if err != nil {
		slog.Error("Failed to create knot", slog.Any("error", err))
	}
}

func NewCreateKnotOptions() *CreateKnotOptions {
	return &CreateKnotOptions{
		Parent:   0,
		Interact: "",
	}
}

func (s *APIV1Service) createKnot(ctx context.Context, request *v1pb.CreateKnotRequest, subject *model.Subject, opts *CreateKnotOptions) (*model.Knot, error) {
	if opts == nil {
		opts = NewCreateKnotOptions()
		if subject != nil {
			opts.SubjectId = subject.ID
			opts.Subject = subject.Matter
		}

		if request.TopicUrn != "" {
			TopicId, err := utils.ExtractIDFromUrn(utils.TopicUrnPrefix, request.TopicUrn)
			if err != nil {
				return nil, status.Errorf(codes.Internal, "failed to get topic id")
			}
			opts.TopicId = TopicId
		}

		opts.Interact = request.InteractType.String()
	}

	currentUser, err := s.GetCurrentUser(ctx)

	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get user")
	}

	if currentUser == nil {
		return nil, status.Errorf(codes.Internal, "failed to get user")
	}

	if request.Visibility < cpb.Visibility_PRIVATE || request.Visibility > cpb.Visibility_PUBLIC {
		request.Visibility = cpb.Visibility_PRIVATE
	}

	create := &model.Knot{
		UID:          "U_" + shortuuid.New(),
		CreatorID:    currentUser.ID,
		Subject:      opts.Subject,
		SubjectID:    opts.SubjectId,
		TopicID:      opts.TopicId,
		InteractType: opts.Interact,
		Content:      request.Content,
		Visibility:   request.Visibility.String(),
		ParentID:     opts.Parent,
		TenantID:     currentUser.TenantID,
	}

	if len(request.Responders) > 0 {
		r := []int32{}
		for _, responder := range request.Responders {
			id, err := utils.ExtractIDFromUrn(utils.UserUrnPrefix, responder)
			if err != nil {
				return nil, status.Errorf(codes.Internal, "failed to get user id")
			}
			u, e := s.Gds.Handler.GetUser(ctx, &apb.FindUser{Id: id})
			if e != nil {
				return nil, status.Errorf(codes.Internal, "failed to get user")
			}
			if u != nil {
				r = append(r, id)
			}
		}
		responderBytes, err := json.Marshal(r)
		if err != nil {
			return nil, status.Errorf(codes.Internal, "failed to marshal responders")
		}

		create.Responders = string(responderBytes)
	}

	return s.insertKnot(ctx, create)
}

func (s *APIV1Service) insertKnot(ctx context.Context, create *model.Knot) (*model.Knot, error) {
	property, err := getKnotPropertyFromContent(create.Content)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get Knot property: %v", err)
	}

	dpd := &apb.KnotPayload{
		Property: property,
	}

	payloadBytes, err := protojson.Marshal(dpd)
	if err != nil {
		return nil, err
	}

	payload := string(payloadBytes)
	create.Payload = payload

	err = s.Gds.Handler.CreateKnot(ctx, create)
	if err != nil {
		return nil, err
	}
	return create, nil
}

func (s *APIV1Service) createAssistKnot(ctx context.Context, assitUserId int32, tenantId int32, parent int32, request *v1pb.CreateKnotRequest) (*model.Knot, error) {

	if request.Visibility < cpb.Visibility_PRIVATE || request.Visibility > cpb.Visibility_PUBLIC {
		request.Visibility = cpb.Visibility_PRIVATE
	}

	create := &model.Knot{
		UID:          "A_" + shortuuid.New(),
		CreatorID:    assitUserId,
		Subject:      request.Subject,
		InteractType: request.InteractType.String(),
		Content:      request.Content,
		ParentID:     parent,
		TenantID:     tenantId,
		Visibility:   request.Visibility.String(),
	}

	return s.insertKnot(ctx, create)
}

func (s *APIV1Service) registerWebsocket(ctx context.Context, srcKnot *model.Knot, subject *model.Subject, content string, history *[]llm.RoleContent) {
	knotName := utils.BuildUrn(utils.KnotUrnPrefix, srcKnot.ID)
	s.WsManager.InitMessage[knotName] = content
	var subjectId int32 = 0
	if srcKnot.SubjectID > 0 {
		subject, err := s.Gds.Handler.GetSubjectById(ctx, srcKnot.SubjectID)
		if err == nil {
			subjectId = subject.ID
		} else {
			slog.Error("Failed to get subject")
			return
		}
	} else if srcKnot.Subject != "" {
		subject, err := s.Gds.Handler.GetSubject(ctx, srcKnot.Subject)
		if err == nil && subject != nil {
			subjectId = subject.ID
		}
	}

	s.WsManager.Register(srcKnot.UID, func(conn *websocket.Conn) {
		slog.Info("WebSocket connection established for knot:", slog.Any("UID", srcKnot.UID))
		outMessageChan := make(chan *v1pb.Exchange, 5) // 缓冲大小可以根据需要调整
		inMessageChan := make(chan *v1pb.Exchange, 5)  // 缓冲大小可以根据需要调整

		// nctx, cancel := context.WithCancel(context.Background())
		nctx, cancel := UserCtxClone(ctx)
		if nctx == nil {
			slog.Error("Failed to clone context")
			return
		}
		userId, err := s.GetCurrentUserId(ctx) // 获取当前用户ID
		if err != nil {
			slog.Error("Failed to get current user ID", slog.Any("error", err))
			return
		}
		chatService := &ChatService{
			// done:      make(chan struct{}),
			userId:    userId,
			api:       s,
			ctx:       nctx,
			subjectId: subjectId,
			subject:   srcKnot.Subject,
		}

		if history != nil {
			chatService.history = history
		}

		defer conn.Close()
		// defer close(chatService.done)
		defer close(inMessageChan)  // 确保在连接关闭时关闭输入通道
		defer close(outMessageChan) // 确保在处理完成后关闭 channel
		defer cancel()              // 确保在处理完成后取消 context
		defer s.WsManager.UnRegister(srcKnot.UID)

		go chatService.processLLMRequest(nctx, subject, srcKnot, inMessageChan, outMessageChan)
		go s.handleWebSocketMessages(nctx, cancel, srcKnot.UID, conn, srcKnot, subject, inMessageChan)

		var ackBuilder strings.Builder
		writeMutex := s.WsManager.MutexMap[srcKnot.UID]

		idleCount := int32(0)
		// WebSocket 消息循环
		for {
			select {
			case message, ok := <-outMessageChan:
				if !ok {
					return
				}

				if message.Phase == v1pb.Phase_Recommend {
					kid, e := utils.ExtractIDFromUrn(utils.KnotUrnPrefix, message.AskId)
					if e != nil {
						continue
					}
					rtm := &model.KnotRecommendTopic{
						ID:     kid,
						Topics: message.Content,
					}
					e = s.Gds.Handler.CreateKnotRT(nctx, rtm)
					if e != nil {
						slog.Error("Failed to create knot recommend topic", slog.Any("error", e))
					}
					continue
				}

				if message.Phase == v1pb.Phase_Continue {
					ackBuilder.WriteString(message.Content)
				}

				if message.Phase == v1pb.Phase_End {
					slog.Info("End phase reached for knot:", "UID", srcKnot.UID)

					if ackBuilder.Len() > 0 {
						ack := ackBuilder.String()
						AskId, err := utils.ExtractIDFromUrn(utils.KnotUrnPrefix, message.AskId)
						if err != nil {
							slog.Error("Error extracting knot ID from name: %v\n", slog.Any("err", err))

							return
						}

						ackKnot, err := s.createAssistKnot(nctx, message.UserId, srcKnot.TenantID, AskId, &v1pb.CreateKnotRequest{Content: ack, Visibility: cpb.Visibility_PRIVATE})
						if err != nil {
							fmt.Printf("Error creating ack knot: %v\n", err)
							return
						}

						message.AnswerId = utils.BuildUrn(utils.KnotUrnPrefix, ackKnot.ID)

						inMsg := &v1pb.Exchange{
							AskId:   message.AnswerId,
							Content: content,
							Phase:   v1pb.Phase_Recommend,
							Mode:    cpb.RagMode_RAG_MODE_UNSPECIFIED,
						}
						select {
						case inMessageChan <- inMsg:
							slog.Info("Recommend Message sent to inMessageChan", slog.Any("ask id", message.AnswerId))
						case <-nctx.Done():
							slog.Warn("Context canceled while sending message to inMessageChan")
							return
						default:
							slog.Warn("Failed to send message to inMessageChan: channel full or closed")
						}
					}

				}

				b, err := proto.Marshal(message)
				if err != nil {
					fmt.Printf("Error marshaling message: %v\n", err)
					return
				}

				sendmsg := func() {
					writeMutex.Lock()
					defer writeMutex.Unlock()

					idleCount = 0

					if err := conn.WriteMessage(websocket.BinaryMessage, b); err != nil {
						// chatService.done <- struct{}{}
						cancel()
						slog.Error("Error writing to WebSocket: %v\n", slog.Any("error", err))
						return
					}
				}
				sendmsg()
			case <-time.After(100 * time.Second):
				idleCount += 1
				if idleCount > 200 {
					slog.Info("wait exchange timeout")
					cancel()
					return
				}
				if idleCount > 199 {
					sendpong := func() {
						defer writeMutex.Unlock()
						writeMutex.Lock()
						conn.WriteMessage(websocket.TextMessage, []byte("xxclosexx"))
					}
					sendpong()
				}

			case <-nctx.Done():
				return
			}
		}
	})
}

func (s *ChatService) processLLMRequest(ctx context.Context, subject *model.Subject, srcKnot *model.Knot, inMessageChan <-chan *v1pb.Exchange, outMessageChan chan<- *v1pb.Exchange) {
	responders := []int32{}
	if srcKnot.Responders != "" && srcKnot.Responders != "{}" {
		err := json.Unmarshal([]byte(srcKnot.Responders), &responders)
		if err != nil {
			slog.Error("Failed to unmarshal responders", slog.Any("error", err))
		}
	}

	if len(responders) == 0 {
		if subject != nil && subject.DefaultResponder == 0 {
			slog.Error("No responder found")
			return
		} else {
			responders = []int32{subject.DefaultResponder}
		}
	}

	p := process.NewProcess(s.api.Gds)
	p.DataPath = s.api.Profile.Data
	wkp, err := p.NewWorkflowProcess(ctx, subject, responders)
	if err != nil {
		slog.Error("Failed to create workflow process", slog.Any("error", err))
		return
	}

	req := &process.LLMReq{
		OutMessageChan: outMessageChan,
		InMessageChan:  inMessageChan,
		History:        s.history,
	}
	wkp.ProcessChat(ctx, req, srcKnot)
}

func (s *APIV1Service) handleWebSocketMessages(ctx context.Context, cancel context.CancelFunc, uid string, conn *websocket.Conn, srcKnot *model.Knot, subject *model.Subject, inMessageChan chan *v1pb.Exchange) {
	writeMutex := s.WsManager.MutexMap[uid]

	sendInputMessage := func(askId string, content string, phase v1pb.Phase, mode cpb.RagMode, resources []*v1pb.Resource) {
		inMsg := &v1pb.Exchange{
			AskId:     askId,
			Content:   content,
			Phase:     phase,
			Mode:      mode,
			Resources: resources,
		}
		select {
		case inMessageChan <- inMsg:
			slog.Info("Message sent to inMessageChan", "message", content)
		case <-ctx.Done():
			return
		default:
			slog.Warn("Failed to send message to inMessageChan: channel full or closed")
		}
	}

	for {
		select {
		case <-ctx.Done():
			return

		default:
			_, msg, err := conn.ReadMessage()
			if err != nil {
				if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
					slog.Warn("WebSocket close error:", slog.Any("err", err))
					cancel()
				} else {
					slog.Warn("WebSocket other error:", slog.Any("err", err))
					cancel()
				}
				return
			}

			if string(msg) == "xxpingxx" {
				sendpong := func() {
					defer writeMutex.Unlock()
					writeMutex.Lock()
					conn.WriteMessage(websocket.TextMessage, []byte("xxpongxx"))
				}
				sendpong()

				continue
			}

			var chatMessage v1pb.Exchange
			if err := proto.Unmarshal(msg, &chatMessage); err != nil {
				slog.Warn("Error unmarshaling message:", slog.Any("err", err))
				continue
			}
			ragMode := cpb.RagMode(cpb.RagMode_value[subject.RagMode])
			if chatMessage.Mode != cpb.RagMode_RAG_MODE_UNSPECIFIED {
				ragMode = chatMessage.Mode
			}
			if chatMessage.Phase == v1pb.Phase_HELLO {
				res, err := s.ListKnotResources(ctx, &v1pb.ListKnotResourcesRequest{
					Urn: utils.BuildUrn(utils.KnotUrnPrefix, srcKnot.ID),
				})
				if err != nil {
					slog.Error("list resource error", slog.Any("err", err))
				}
				content := chatMessage.Content
				if content == "" {
					content = s.WsManager.InitMessage[chatMessage.AskId]
					if content == "" {
						slog.Info("hello phase message is empty ", chatMessage.AskId, content)
						continue
					}
					slog.Info("hello init message", chatMessage.AskId, content)
					sendInputMessage(chatMessage.AskId, content, chatMessage.Phase, ragMode, res.Resources)
					continue
				}

			} else if chatMessage.Phase == v1pb.Phase_End {
				sendInputMessage(chatMessage.AskId, "", chatMessage.Phase, ragMode, chatMessage.Resources)
				continue
			} else if chatMessage.Phase == v1pb.Phase_Close {
				slog.Info("close phase message")
				cancel()
				return
			}

			trimmedContent := strings.TrimSpace(chatMessage.Content)

			if trimmedContent == "" {
				continue
			}

			var askKnot *model.Knot
			if chatMessage.AskId != "" {
				i, err := utils.ExtractIDFromUrn(utils.KnotUrnPrefix, chatMessage.AskId)
				if err != nil {
					slog.Warn("Error extracting ID from URN:", slog.Any("err", err))
				}
				askKnot, err = s.Gds.Handler.GetKnot(ctx, &apb.FindKnot{Id: i})
				if err != nil {
					slog.Warn("Error getting knot:", slog.Any("err", err))
				}
			} else {
				opts := &CreateKnotOptions{
					Parent:    srcKnot.ID,
					Interact:  cpb.InteractType_CHAT.String(),
					SubjectId: srcKnot.SubjectID,
					Subject:   srcKnot.Subject,
					TopicId:   srcKnot.TopicID}
				askKnot, err = s.createKnot(ctx, &v1pb.CreateKnotRequest{Content: chatMessage.Content}, subject, opts)

				if err != nil {
					slog.Warn("Error creating knot:", slog.Any("err", err))

				}
			}

			if askKnot == nil {
				slog.Error("ask Knot is nil")
				continue
			}

			sendInputMessage(utils.BuildUrn(utils.KnotUrnPrefix, askKnot.ID), chatMessage.Content, chatMessage.Phase, ragMode, chatMessage.Resources)
		}
	}
}

func (s *APIV1Service) isPinned(ctx context.Context, request *apb.KnotArrangement) bool {
	knots, err := s.Gds.Handler.ListKnotArrangement(ctx, request)
	if err != nil {
		return false
	}

	if len(knots) > 0 {
		return true
	}

	return false
}

func (s *APIV1Service) convertKnotFromDB(ctx context.Context, knot *model.Knot) (*v1pb.Knot, error) {
	displayTs := knot.CreatedTs
	systemRelatedSetting, _ := s.GetSystemKnotRelatedSetting(ctx)
	if systemRelatedSetting == nil {
		return nil, errors.New("failed to get system Knot related setting")
	}

	if systemRelatedSetting.DisplayWithUpdateTime {
		displayTs = knot.UpdatedTs
	}

	urn := utils.BuildUrn(utils.KnotUrnPrefix, knot.ID)
	listKnotRelationsResponse, err := s.ListKnotRelations(ctx, &v1pb.ListKnotRelationsRequest{Urn: urn})
	if err != nil {
		return nil, errors.Wrap(err, "failed to list Knot relations")
	}

	listKnotResourcesResponse, err := s.ListKnotResources(ctx, &v1pb.ListKnotResourcesRequest{Urn: urn})
	if err != nil {
		return nil, errors.Wrap(err, "failed to list Knot resources")
	}

	listKnotReactionsResponse, err := s.ListKnotReactions(ctx, &v1pb.ListKnotReactionsRequest{Urn: urn})
	if err != nil {
		return nil, errors.Wrap(err, "failed to list Knot reactions")
	}

	nodes, err := parser.Parse(tokenizer.Tokenize(knot.Content))
	if err != nil {
		return nil, errors.Wrap(err, "failed to parse content")
	}

	snippet, err := getKnotContentSnippet(knot.Content)
	if err != nil {
		return nil, errors.Wrap(err, "failed to get Knot content snippet")
	}

	knotMsg := &v1pb.Knot{
		Urn:          urn,
		Uid:          knot.UID,
		RowStatus:    convertRowStatusToEnum(knot.RowStatus),
		Creator:      utils.BuildUrn(utils.UserUrnPrefix, knot.CreatorID),
		CreateTime:   timestamppb.New(time.Unix(knot.CreatedTs, 0)),
		UpdateTime:   timestamppb.New(time.Unix(knot.UpdatedTs, 0)),
		DisplayTime:  timestamppb.New(time.Unix(displayTs, 0)),
		Content:      knot.Content,
		Snippet:      snippet,
		Nodes:        convertFromASTNodes(nodes),
		Visibility:   cpb.Visibility(cpb.Visibility_value[knot.Visibility]),
		Pinned:       s.isPinned(ctx, &apb.KnotArrangement{KnotId: knot.ID, UserId: 1}),
		Relations:    listKnotRelationsResponse.Relations,
		Resources:    listKnotResourcesResponse.Resources,
		Reactions:    listKnotReactionsResponse.Reactions,
		Subject:      knot.Subject,
		SubjectUrn:   utils.BuildUrn(utils.SubjectUrnPrefix, knot.SubjectID),
		TopicUrn:     utils.BuildUrn(utils.TopicUrnPrefix, knot.TopicID),
		InteractType: cpb.InteractType(cpb.InteractType_value[knot.InteractType]),
	}

	if knot.Responders != "" && knot.Responders != "{}" {
		responders := []int32{}
		err := json.Unmarshal([]byte(knot.Responders), &responders)
		if err != nil {
			return nil, errors.Wrap(err, "failed to unmarshal responders")
		}

		respondersStr := []string{}
		for _, responder := range responders {
			respondersStr = append(respondersStr, utils.BuildUrn(utils.UserUrnPrefix, responder))
		}
		knotMsg.Responders = respondersStr
	}

	if len(knot.Payload) > 0 {
		kpload := &apb.KnotPayload{}
		err := protojson.Unmarshal([]byte(knot.Payload), kpload)
		if err != nil {
			return nil, err
		}
		knotMsg.Property = convertKnotPropertyFromDB(kpload.Property)
	}

	if knot.ParentID > 0 {
		p := utils.BuildUrn(utils.KnotUrnPrefix, knot.ParentID)
		knotMsg.Parent = &p
	}

	return knotMsg, nil
}

func (s *APIV1Service) ListKnots(ctx context.Context, request *v1pb.ListKnotsRequest) (*v1pb.ListKnotsResponse, error) {
	user, err := s.GetCurrentUser(ctx)
	if err != nil {
		return nil, status.Errorf(codes.Unauthenticated, "failed to get current user: %v", err)
	}

	knotFind := &apb.FindKnot{
		// Exclude comments by default.
		ExcludeComments: true,
		ExcludeChildren: true,

		CurrentUserId:   user.ID,
		CurrentUserRole: cpb.Role(cpb.Role_value[user.Role]),
	}

	if err := s.buildKnotFindWithFilter(ctx, knotFind, request.Filter); err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "failed to build find knots with filter: %v", err)
	}

	var limit, offset int32
	if request.PageToken != nil {
		limit = request.PageToken.Limit
		offset = request.PageToken.Offset
	} else {
		limit = request.PageSize
	}
	if limit <= 0 {
		limit = DefaultPageSize
	}
	limitPlusOne := limit
	knotFind.Limit = limitPlusOne
	knotFind.Offset = offset
	knosts, count, err := s.Gds.Handler.ListKnots(ctx, knotFind)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to list knots: %v", err)
	}

	knotMessages := []*v1pb.Knot{}

	if int32(len(knosts)) == limitPlusOne {
		knosts = knosts[:limit]
		if err != nil {
			return nil, status.Errorf(codes.Internal, "failed to get next page token, error: %v", err)
		}
	}
	for _, Knot := range knosts {
		knotMessage, err := s.convertKnotFromDB(ctx, Knot)
		if err != nil {
			return nil, errors.Wrap(err, "failed to convert Knot")
		}
		knotMessages = append(knotMessages, knotMessage)
	}

	response := &v1pb.ListKnotsResponse{
		TotalSize: count,
		Knots:     knotMessages,
		NextPageToken: &cpb.PageToken{
			Limit:  int32(limit),
			Offset: int32(offset),
		},
	}
	return response, nil
}

func (s *APIV1Service) GetKnot(ctx context.Context, request *v1pb.GetKnotRequest) (*v1pb.Knot, error) {
	id, err := utils.ExtractIDFromUrn(utils.KnotUrnPrefix, request.Urn)
	if err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "invalid Knot urn: %v", err)
	}
	Knot, err := s.Gds.Handler.GetKnot(ctx, &apb.FindKnot{
		Id: id,
	})
	if err != nil {
		return nil, err
	}
	if Knot == nil {
		return nil, status.Errorf(codes.NotFound, "Knot not found")
	}

	visibility := cpb.Visibility_value[Knot.Visibility]
	if visibility != int32(cpb.Visibility_PUBLIC) {
		user, err := s.GetCurrentUser(ctx)
		if err != nil {
			return nil, status.Errorf(codes.Internal, "failed to get user")
		}
		if user == nil {
			return nil, status.Errorf(codes.PermissionDenied, "permission denied")
		}
		if visibility == int32(cpb.Visibility_PRIVATE) && Knot.CreatorID != user.ID {
			return nil, status.Errorf(codes.PermissionDenied, "permission denied")
		}
	}

	knotMsg, err := s.convertKnotFromDB(ctx, Knot)
	if err != nil {
		return nil, errors.Wrap(err, "failed to convert Knot")
	}
	return knotMsg, nil
}

//nolint:all
func (s *APIV1Service) GetKnotByUid(ctx context.Context, request *v1pb.GetKnotByUidRequest) (*v1pb.Knot, error) {
	Knot, err := s.Gds.Handler.GetKnot(ctx, &apb.FindKnot{
		Uid: request.Uid,
	})

	if err != nil {
		return nil, err
	}
	if Knot == nil {
		return nil, status.Errorf(codes.NotFound, "Knot not found")
	}

	visibility := cpb.Visibility_value[Knot.Visibility]
	if visibility != int32(cpb.Visibility_PUBLIC) {
		user, err := s.GetCurrentUser(ctx)
		if err != nil {
			return nil, status.Errorf(codes.Internal, "failed to get user")
		}
		if user == nil {
			return nil, status.Errorf(codes.PermissionDenied, "permission denied")
		}
		if visibility == int32(cpb.Visibility_PRIVATE) && Knot.CreatorID != user.ID {
			return nil, status.Errorf(codes.PermissionDenied, "permission denied")
		}
	}

	knotMsg, err := s.convertKnotFromDB(ctx, Knot)
	if err != nil {
		return nil, errors.Wrap(err, "failed to convert Knot")
	}
	return knotMsg, nil
}

func (s *APIV1Service) UpdateKnot(ctx context.Context, request *v1pb.UpdateKnotRequest) (*v1pb.Knot, error) {
	id, err := utils.ExtractIDFromUrn(utils.KnotUrnPrefix, request.Knot.Urn)
	if err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "invalid Knot urn: %v", err)
	}
	if request.UpdateMask == nil || len(request.UpdateMask.Paths) == 0 {
		return nil, status.Errorf(codes.InvalidArgument, "update mask is required")
	}

	Knot, err := s.Gds.Handler.GetKnot(ctx, &apb.FindKnot{Id: id})
	if err != nil {
		return nil, err
	}
	if Knot == nil {
		return nil, status.Errorf(codes.NotFound, "Knot not found")
	}

	user, err := s.GetCurrentUser(ctx)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get current user")
	}
	if Knot.CreatorID != user.ID {
		return nil, status.Errorf(codes.PermissionDenied, "permission denied")
	}

	update := &apb.UpdateKnot{
		Id:        id,
		UpdatedTs: timestamppb.Now(),
	}

	for _, path := range request.UpdateMask.Paths {
		if path == "content" {
			contentLengthLimit, err := s.getContentLengthLimit(ctx)
			if err != nil {
				return nil, status.Errorf(codes.Internal, "failed to get content length limit")
			}
			if len(request.Knot.Content) > contentLengthLimit {
				return nil, status.Errorf(codes.InvalidArgument, "content too long (max %d characters)", contentLengthLimit)
			}
			update.Content = request.Knot.Content

			property, err := getKnotPropertyFromContent(update.Content)
			if err != nil {
				return nil, status.Errorf(codes.Internal, "failed to get Knot property: %v", err)
			}
			payload := &apb.KnotPayload{
				Property: property,
			}
			payload.Property = property
			update.Payload = payload
		} else if path == "uid" {
			update.Uid = request.Knot.Uid
			if !utils.UIDMatcher.MatchString(update.Uid) {
				return nil, status.Errorf(codes.InvalidArgument, "invalid resource name")
			}
		} else if path == "visibility" {
			systemKnotRelatedSetting, err := s.GetSystemKnotRelatedSetting(ctx)
			if err != nil {
				return nil, status.Errorf(codes.Internal, "failed to get workspace Knot related setting")
			}

			if systemKnotRelatedSetting.DisallowPublicVisibility && request.Knot.Visibility == cpb.Visibility_PUBLIC {
				return nil, status.Errorf(codes.PermissionDenied, "disable public knots system setting is enabled")
			}
			update.Visibility = request.Knot.Visibility
		} else if path == "row_status" {
			update.RowStatus = request.Knot.RowStatus
		} else if path == "create_time" {
			update.CreatedTs = request.Knot.CreateTime
		} else if path == "display_time" {
			knotelatedSetting, err := s.GetSystemKnotRelatedSetting(ctx)
			if err != nil {
				return nil, status.Errorf(codes.Internal, "failed to get workspace Knot related setting")
			}
			if knotelatedSetting.DisplayWithUpdateTime {
				update.UpdatedTs = request.Knot.DisplayTime
			} else {
				update.CreatedTs = request.Knot.DisplayTime
			}
			update.UpdatedTs = request.Knot.DisplayTime
		} else if path == "pinned" {

			ka := &model.KnotArrangement{
				KnotID: id,
				UserID: user.ID,
				Pinned: request.Knot.Pinned,
			}
			err := s.Gds.Handler.UpsertKnotArrangement(ctx, ka)
			if err != nil {
				return nil, status.Errorf(codes.Internal, "failed to upsert Knot organizer")
			}
		}
	}

	if err = s.Gds.Handler.UpdateKnot(ctx, update); err != nil {
		return nil, status.Errorf(codes.Internal, "failed to update Knot")
	}

	Knot, err = s.Gds.Handler.GetKnot(ctx, &apb.FindKnot{
		Id: id,
	})
	if err != nil {
		return nil, errors.Wrap(err, "failed to get Knot")
	}

	knotMsg, err := s.convertKnotFromDB(ctx, Knot)
	if err != nil {
		return nil, errors.Wrap(err, "failed to convert Knot")
	}
	// Try to dispatch webhook when Knot is updated.
	if err := s.DispatchKnotUpdatedWebhook(ctx, knotMsg); err != nil {
		slog.Warn("Failed to dispatch Knot updated webhook", slog.Any("err", err))
	}

	return knotMsg, nil
}

func (s *APIV1Service) DeleteKnot(ctx context.Context, request *v1pb.DeleteKnotRequest) (*emptypb.Empty, error) {
	id, err := utils.ExtractIDFromUrn(utils.KnotUrnPrefix, request.Urn)
	if err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "invalid Knot urn: %v", err)
	}
	Knot, err := s.Gds.Handler.GetKnot(ctx, &apb.FindKnot{
		Id: id,
	})
	if err != nil {
		return nil, err
	}
	if Knot == nil {
		return nil, status.Errorf(codes.NotFound, "Knot not found")
	}

	user, err := s.GetCurrentUser(ctx)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get current user")
	}
	if Knot.CreatorID != user.ID {
		return nil, status.Errorf(codes.PermissionDenied, "permission denied")
	}

	if knotMsg, err := s.convertKnotFromDB(ctx, Knot); err == nil {
		// Try to dispatch webhook when Knot is deleted.
		if err := s.DispatchKnotDeletedWebhook(ctx, knotMsg); err != nil {
			slog.Warn("Failed to dispatch Knot deleted webhook", slog.Any("err", err))
		}
	}

	// // Delete Knot relation
	if err := s.Gds.Handler.DeleteKnotRelation(ctx, &model.KnotRelation{KnotID: id}); err != nil {
		return nil, status.Errorf(codes.Internal, "failed to delete Knot relations")
	}

	if err := s.Gds.Handler.DeleteReaction(ctx, &apb.DeleteReaction{KnotId: id}); err != nil {
		return nil, status.Errorf(codes.Internal, "failed to delete Knot relations")
	}

	if err = s.Gds.Handler.DeleteKnot(ctx, &apb.DeleteKnot{Id: id}); err != nil {
		return nil, status.Errorf(codes.Internal, "failed to delete Knot")
	}

	// Delete related resources.
	resources, _, err := s.Gds.Handler.ListResources(ctx, &apb.FindResource{KnotId: id})
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to list resources")
	}

	for _, resource := range resources {
		if err := s.Gds.Handler.DeleteResource(ctx, &apb.DeleteResource{Id: resource.ID}); err != nil {
			return nil, status.Errorf(codes.Internal, "failed to delete resource")
		}
	}

	// Delete Knot child
	err = s.deleteKnotChild(ctx, id)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to delete Knot child")
	}

	// Delete Knot references
	// referenceType := v1pb.KnotRelation_REFERENCE.String()
	if err := s.Gds.Handler.DeleteKnotRelation(ctx, &model.KnotRelation{RelatedKnotID: id, InteractType: cpb.InteractType_REFERENCE.String()}); err != nil {
		return nil, status.Errorf(codes.Internal, "failed to delete Knot references")
	}

	return &emptypb.Empty{}, nil
}

func (s *APIV1Service) CreateKnotComment(ctx context.Context, request *v1pb.CreateKnotCommentRequest) (*v1pb.Knot, error) {
	id, err := utils.ExtractIDFromUrn(utils.KnotUrnPrefix, request.Urn)
	if err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "invalid Knot urn: %v", err)
	}
	relatedKnot, err := s.Gds.Handler.GetKnot(ctx, &apb.FindKnot{Id: id})
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get Knot")
	}
	var subject *model.Subject
	if relatedKnot.SubjectID != 0 {
		subject, err = s.Gds.Handler.GetSubjectById(ctx, relatedKnot.SubjectID)
		if err != nil {
			return nil, status.Errorf(codes.Internal, "failed to get subject")
		}
	}
	// Create the comment Knot first.
	knot, err := s.createKnot(ctx, request.Comment, subject, &CreateKnotOptions{Parent: relatedKnot.ID, Interact: cpb.InteractType_COMMENT.String()})
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to create Knot")
	}

	if len(request.Comment.Resources) > 0 {
		_, err := s.SetKnotResources(ctx, &v1pb.SetKnotResourcesRequest{
			Urn:       utils.BuildUrn(utils.KnotUrnPrefix, knot.ID),
			Resources: request.Comment.Resources,
		})
		if err != nil {
			return nil, errors.Wrap(err, "failed to set knot resources")
		}
	}

	knotMsg, err := s.convertKnotFromDB(ctx, knot)

	if knotMsg.Visibility != cpb.Visibility_PRIVATE && knot.CreatorID != relatedKnot.CreatorID {
		pd := apb.ActivityPayload{
			KnotComment: &apb.ActivityKnotCommentPayload{
				KnotId:        knot.ID,
				RelatedKnotId: relatedKnot.ID,
			},
		}

		pds, err := protojson.Marshal(&pd)
		if err != nil {
			return nil, status.Errorf(codes.Internal, "failed to marshal payload")
		}
		activity := &model.Activity{
			CreatorID: knot.CreatorID,
			Type:      apb.ActivityType_KNOT_COMMENT.String(),
			Level:     apb.ActivityLevel_ACTIVITY_INFO.String(),
			Payload:   string(pds),
		}

		err = s.Gds.Handler.CreateActivity(ctx, activity)

		if err != nil {
			return nil, status.Errorf(codes.Internal, "failed to create activity")
		}

		boxmsg := &apb.InboxMessage{
			Type:       apb.InboxMessage_KNOT_COMMENT,
			ActivityId: &activity.ID,
		}

		boxstr, err := protojson.Marshal(boxmsg)
		if err != nil {
			return nil, status.Errorf(codes.Internal, "failed to marshal inbox message")
		}

		inbox := &model.Inbox{
			SenderID:   knot.CreatorID,
			ReceiverID: relatedKnot.CreatorID,
			Status:     cpb.RowStatus_ACTIVE.String(),
			Message:    string(boxstr),
		}

		err = s.Gds.Handler.CreateInbox(ctx, inbox)
		if err != nil {
			return nil, status.Errorf(codes.Internal, "failed to create inbox")
		}
	}

	return knotMsg, nil
}

func (s *APIV1Service) CreateKnotChat(ctx context.Context, request *v1pb.CreateKnotChatRequest) (*v1pb.Knot, error) {
	id, err := utils.ExtractIDFromUrn(utils.KnotUrnPrefix, request.Urn)
	if err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "invalid Knot urn: %v", err)
	}
	relatedKnot, err := s.Gds.Handler.GetKnot(ctx, &apb.FindKnot{Id: id})
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get Knot")
	}

	var subject *model.Subject
	if relatedKnot.SubjectID > 0 {
		subject, err = s.Gds.Handler.GetSubjectById(ctx, relatedKnot.SubjectID)
		if err != nil {
			return nil, status.Errorf(codes.Internal, "failed to get subject")
		}
	}

	// Create the chat Knot first.
	opts := &CreateKnotOptions{Parent: relatedKnot.ID, Interact: cpb.InteractType_CHAT.String()}

	if relatedKnot.SubjectID > 0 {
		opts.SubjectId = relatedKnot.SubjectID
		opts.Subject = relatedKnot.Subject
	}

	if relatedKnot.TopicID > 0 {
		request.Chat.TopicUrn = utils.BuildUrn(utils.TopicUrnPrefix, relatedKnot.TopicID)
		opts.TopicId = relatedKnot.TopicID
	}

	// Create the chat Knot first.
	knot, err := s.createKnot(ctx, request.Chat, subject, opts)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to create Knot")
	}

	if len(request.Chat.Resources) > 0 {
		_, err := s.SetKnotResources(ctx, &v1pb.SetKnotResourcesRequest{
			Urn:       utils.BuildUrn(utils.KnotUrnPrefix, knot.ID),
			Resources: request.Chat.Resources,
		})
		if err != nil {
			return nil, errors.Wrap(err, "failed to set knot resources")
		}
	}

	knotMsg, err := s.convertKnotFromDB(ctx, knot)
	if knotMsg.Visibility != cpb.Visibility_PRIVATE && knot.CreatorID != relatedKnot.CreatorID {
		pd := apb.ActivityPayload{
			KnotComment: &apb.ActivityKnotCommentPayload{
				KnotId:        knot.ID,
				RelatedKnotId: relatedKnot.ID,
			},
		}

		pds, err := protojson.Marshal(&pd)
		if err != nil {
			return nil, status.Errorf(codes.Internal, "failed to marshal payload")
		}

		activity := &model.Activity{
			CreatorID: knot.CreatorID,
			Type:      apb.ActivityType_KNOT_COMMENT.String(),
			Level:     apb.ActivityLevel_ACTIVITY_INFO.String(),
			Payload:   string(pds),
		}

		err = s.Gds.Handler.CreateActivity(ctx, activity)
		if err != nil {
			return nil, status.Errorf(codes.Internal, "failed to create activity")
		}

		boxmsg := &apb.InboxMessage{
			Type:       apb.InboxMessage_KNOT_COMMENT,
			ActivityId: &activity.ID,
		}

		boxstr, err := protojson.Marshal(boxmsg)
		if err != nil {
			return nil, status.Errorf(codes.Internal, "failed to marshal inbox message")
		}

		inbox := &model.Inbox{
			SenderID:   knot.CreatorID,
			ReceiverID: relatedKnot.CreatorID,
			Status:     cpb.RowStatus_ACTIVE.String(),
			Message:    string(boxstr),
		}

		err = s.Gds.Handler.CreateInbox(ctx, inbox)
		if err != nil {
			return nil, status.Errorf(codes.Internal, "failed to create inbox")
		}
	}

	if s.WsManager != nil {
		if !s.WsManager.HaveHandler(relatedKnot.UID) {
			historyKnots, _, err := s.Gds.Handler.ListKnots(ctx, &apb.FindKnot{
				ParentId: relatedKnot.ID})
			if err != nil {
				return nil, status.Errorf(codes.Internal, "failed to list Knots")
			}

			messages := &[]llm.RoleContent{}

			for _, historyKnot := range historyKnots {
				m := llm.RoleContent{
					Role:    llm.RoleAssistant,
					Content: historyKnot.Content,
				}
				if strings.HasPrefix(historyKnot.UID, "U_") {
					m.Role = llm.RoleUser
				}
				*messages = append(*messages, m)

			}
			s.registerWebsocket(ctx, relatedKnot, subject, request.Chat.Content, messages)
		}
	}
	return knotMsg, nil
}

func (s *APIV1Service) ListKnotChats(ctx context.Context, request *v1pb.ListKnotChatsRequest) (*v1pb.ListKnotChatsResponse, error) {
	id, err := utils.ExtractIDFromUrn(utils.KnotUrnPrefix, request.Urn)
	if err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "invalid Knot urn: %v", err)
	}

	chats, _, err := s.Gds.Handler.ListKnots(ctx, &apb.FindKnot{
		ExcludeChildren: false,
		ParentId:        id})
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to list Knot relations")
	}

	var knots []*v1pb.Knot
	for _, chat := range chats {
		knotMessage, err := s.convertKnotFromDB(ctx, chat)
		if err != nil {
			return nil, errors.Wrap(err, "failed to convert Knot")
		}
		knots = append(knots, knotMessage)
	}

	response := &v1pb.ListKnotChatsResponse{
		Knots: knots,
	}
	return response, nil
}

func (s *APIV1Service) ListKnotExchanges(ctx context.Context, request *v1pb.ListKnotExchangesRequest) (*v1pb.ListKnotExchangesResponse, error) {
	currentUser, err := s.GetCurrentUser(ctx)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
	}

	id, err := utils.ExtractIDFromUrn(utils.KnotUrnPrefix, request.Urn)
	if err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "invalid Knot urn: %v", err)
	}

	rootKnot, err := s.Gds.Handler.GetKnot(ctx, &apb.FindKnot{Id: id})
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get Knot")
	}

	if currentUser.TenantID > 0 && rootKnot.TenantID != currentUser.TenantID {
		return nil, status.Errorf(codes.PermissionDenied, "permission denied")
	}

	chats, _, err := s.Gds.Handler.ListKnots(ctx, &apb.FindKnot{
		ExcludeChildren: false,
		ParentId:        id,
		OrderByTimeAsc:  true,
		Visibilities:    []cpb.Visibility{cpb.Visibility_PUBLIC, cpb.Visibility_PRIVATE, cpb.Visibility_TENANT},
		Limit:           0xFFFF})
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to list Knot relations")
	}
	// if len(chats) == 0 {
	// 	return &v1pb.ListKnotExchangesResponse{}, nil
	// }
	res := &v1pb.ListKnotExchangesResponse{}
	records := make([]*v1pb.P2MExchangeRecord, 0)

	record := &v1pb.P2MExchangeRecord{
		Subject:    rootKnot.Subject,
		SubjectUrn: utils.BuildUrn(utils.SubjectUrnPrefix, rootKnot.SubjectID),
		Topic:      utils.BuildUrn(utils.TopicUrnPrefix, rootKnot.TopicID),
		RootUrn:    request.Urn,
		RootUid:    rootKnot.UID,
		P2M:        false,
	}

	records = append(records, record)
	s.BuildKnotExchanges(ctx, currentUser.ID, rootKnot, &record.Records, chats)

	res.Records = records
	return res, nil
}

func (s *APIV1Service) BuildKnotExchanges(ctx context.Context, currentId int32, rootKnot *model.Knot, records *[]*v1pb.P2MExchangeReocrdUnit, chats []*model.Knot) error {
	nodes, err := parser.Parse(tokenizer.Tokenize(rootKnot.Content))
	if err != nil {
		return errors.Wrap(err, "failed to parse content")
	}

	listAskResourcesResponse, err := s.ListKnotResources(ctx, &v1pb.ListKnotResourcesRequest{Urn: utils.BuildUrn(utils.KnotUrnPrefix, rootKnot.ID)})
	if err != nil {
		return errors.Wrap(err, "failed to list knot resources")
	}

	ask := &v1pb.RecordUnit{
		Content:    rootKnot.Content,
		CreateTime: timestamppb.New(time.Unix(rootKnot.CreatedTs, 0)),
		Nodes:      convertFromASTNodes(nodes),
		Resources:  listAskResourcesResponse.Resources,
	}

	if len(chats) == 0 {
		recordUnit := &v1pb.P2MExchangeReocrdUnit{
			AskId:     utils.BuildUrn(utils.KnotUrnPrefix, rootKnot.ID),
			RagMode:   cpb.RagMode_RAG_MODE_UNSPECIFIED,
			Phase:     v1pb.Phase_End,
			AskRecord: ask,
		}

		*records = append(*records, recordUnit)
		return nil
	}

	nextAsks := []*model.Knot{}
	for _, chat := range chats {
		if chat.CreatorID == currentId {
			nextAsks = append(nextAsks, chat)
		} else {

			answerNodes, err := parser.Parse(tokenizer.Tokenize(chat.Content))
			if err != nil {
				return errors.Wrap(err, "failed to parse content")
			}

			listAnswerResourcesResponse, err := s.ListKnotResources(ctx, &v1pb.ListKnotResourcesRequest{Urn: utils.BuildUrn(utils.KnotUrnPrefix, chat.ID)})
			if err != nil {
				return errors.Wrap(err, "failed to list knot resources")
			}

			awu := &v1pb.UserUnit{}
			u, e := s.Gds.Handler.GetUser(ctx, &apb.FindUser{Id: chat.CreatorID})
			if e != nil {
				return errors.Wrap(err, "failed to get user")
			}
			if u != nil {
				awu = &v1pb.UserUnit{
					Urn:    utils.BuildUrn(utils.UserUrnPrefix, u.ID),
					Name:   u.Username,
					Avatar: u.AvatarURL,
				}
			}
			answer := &v1pb.UserRecordUnit{
				Content:    chat.Content,
				CreateTime: timestamppb.New(time.Unix(chat.CreatedTs, 0)),
				Nodes:      convertFromASTNodes(answerNodes),
				Resources:  listAnswerResourcesResponse.Resources,
				User:       awu,
			}
			recordUnit := &v1pb.P2MExchangeReocrdUnit{
				AskId:        utils.BuildUrn(utils.KnotUrnPrefix, rootKnot.ID),
				AnswerId:     utils.BuildUrn(utils.KnotUrnPrefix, chat.ID),
				RagMode:      cpb.RagMode_RAG_MODE_UNSPECIFIED,
				Phase:        v1pb.Phase_End,
				AskRecord:    ask,
				AnswerRecord: answer,
			}

			*records = append(*records, recordUnit)
		}
	}
	if len(nextAsks) > 0 {
		for _, ask := range nextAsks {
			nchats, _, err := s.Gds.Handler.ListKnots(ctx, &apb.FindKnot{
				ExcludeChildren: false,
				ParentId:        ask.ID,
				OrderByTimeAsc:  true,
				Visibilities:    []cpb.Visibility{cpb.Visibility_PUBLIC, cpb.Visibility_PRIVATE, cpb.Visibility_TENANT},
				Limit:           0xFFFF})
			if err != nil {
				return status.Errorf(codes.Internal, "failed to list Knot relations")
			}

			s.BuildKnotExchanges(ctx, ask.CreatorID, ask, records, nchats)
		}
	}
	return nil
}

func (s *APIV1Service) ExportKnots(ctx context.Context, request *v1pb.ExportKnotsRequest) (*v1pb.ExportKnotsResponse, error) {
	knotFind := &apb.FindKnot{
		RowStatus: cpb.RowStatus_ACTIVE,
		// Exclude comments by default.
		ExcludeComments: true,
	}
	if err := s.buildKnotFindWithFilter(ctx, knotFind, request.Filter); err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "failed to build find knots with filter: %v", err)
	}

	knots, count, err := s.Gds.Handler.ListKnots(ctx, knotFind)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to list knots: %v", err)
	}

	if count == 0 {
		return &v1pb.ExportKnotsResponse{}, nil
	}

	buf := new(bytes.Buffer)
	writer := zip.NewWriter(buf)
	for _, Knot := range knots {
		knotMessage, err := s.convertKnotFromDB(ctx, Knot)
		if err != nil {
			return nil, errors.Wrap(err, "failed to convert Knot")
		}
		file, err := writer.Create(time.Unix(Knot.CreatedTs, 0).Format(time.RFC3339) + "-" + Knot.UID + "-" + string(Knot.Visibility) + ".md")
		if err != nil {
			return nil, status.Errorf(codes.Internal, "Failed to create Knot file")
		}
		_, err = file.Write([]byte(knotMessage.Content))
		if err != nil {
			return nil, status.Errorf(codes.Internal, "Failed to write to Knot file")
		}
	}
	if err := writer.Close(); err != nil {
		return nil, status.Errorf(codes.Internal, "Failed to close zip file writer")
	}

	return &v1pb.ExportKnotsResponse{
		Content: buf.Bytes(),
	}, nil
}

func (s *APIV1Service) ListKnotProperties(ctx context.Context, request *v1pb.ListKnotPropertiesRequest) (*v1pb.ListKnotPropertiesResponse, error) {
	user, err := s.GetCurrentUser(ctx)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get current user")
	}

	knotFind := &apb.FindKnot{
		CreatorId:       user.ID,
		RowStatus:       cpb.RowStatus_ACTIVE,
		ExcludeComments: true,
		// Default exclude content for performance.
		ExcludeContent: true,
	}
	if request.Urn != "knots/-" {
		knotID, err := utils.ExtractIDFromUrn(utils.KnotUrnPrefix, request.Urn)
		if err != nil {
			return nil, status.Errorf(codes.InvalidArgument, "invalid Knot urn: %v", err)
		}
		knotFind.Id = knotID
	}

	knots, _, err := s.Gds.Handler.ListKnots(ctx, knotFind)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to list knots")
	}
	systemKnotRelatedSetting, _ := s.GetSystemKnotRelatedSetting(ctx)

	entities := []*v1pb.KnotPropertyEntity{}
	for _, Knot := range knots {
		displayTs := Knot.CreatedTs
		if systemKnotRelatedSetting.DisplayWithUpdateTime {
			displayTs = Knot.UpdatedTs
		}
		entity := &v1pb.KnotPropertyEntity{
			Urn:         utils.BuildUrn(utils.KnotUrnPrefix, Knot.ID),
			DisplayTime: timestamppb.New(time.Unix(displayTs, 0)),
		}

		kpload := &apb.KnotPayload{}
		err := protojson.Unmarshal([]byte(Knot.Payload), kpload)
		if err != nil {
			return nil, err
		}
		entity.Property = convertKnotPropertyFromDB(kpload.Property)
		entities = append(entities, entity)
	}
	return &v1pb.ListKnotPropertiesResponse{
		Entities: entities,
	}, nil
}

func (s *APIV1Service) RebuildKnotProperty(ctx context.Context, request *v1pb.RebuildKnotPropertyRequest) (*emptypb.Empty, error) {
	user, err := s.GetCurrentUser(ctx)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get current user")
	}

	knotFind := &apb.FindKnot{
		CreatorId:       user.ID,
		RowStatus:       cpb.RowStatus_ACTIVE,
		ExcludeComments: true,
	}
	if (request.Urn) != "knots/-" {
		knotID, err := utils.ExtractIDFromUrn(utils.KnotUrnPrefix, request.Urn)
		if err != nil {
			return nil, status.Errorf(codes.InvalidArgument, "invalid Knot urn: %v", err)
		}
		knotFind.Id = knotID
	}

	knots, _, err := s.Gds.Handler.ListKnots(ctx, knotFind)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to list knots")
	}

	for _, Knot := range knots {
		property, err := getKnotPropertyFromContent(Knot.Content)
		if err != nil {
			return nil, status.Errorf(codes.Internal, "failed to get Knot property: %v", err)
		}

		dpd := &apb.KnotPayload{
			Property: property,
		}

		if err := s.Gds.Handler.UpdateKnot(ctx, &apb.UpdateKnot{
			Id:      Knot.ID,
			Payload: dpd,
		}); err != nil {
			return nil, status.Errorf(codes.Internal, "failed to update Knot")
		}
	}

	return &emptypb.Empty{}, nil
	// return nil, nil
}

func (s *APIV1Service) ListKnotTags(ctx context.Context, request *v1pb.ListKnotTagsRequest) (*v1pb.ListKnotTagsResponse, error) {
	knotFind := &apb.FindKnot{
		RowStatus:       cpb.RowStatus_ACTIVE,
		ExcludeComments: true,
		// Default exclude content for performance.
		ExcludeContent: true,
	}
	if (request.Parent) != "knots/-" {
		knotID, err := utils.ExtractIDFromUrn(utils.KnotUrnPrefix, request.Parent)
		if err != nil {
			return nil, status.Errorf(codes.InvalidArgument, "invalid Knot urn: %v", err)
		}
		knotFind.Id = knotID
	}
	if err := s.buildKnotFindWithFilter(ctx, knotFind, request.Filter); err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "failed to build find knots with filter: %v", err)
	}

	knots, _, err := s.Gds.Handler.ListKnots(ctx, knotFind)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to list knots")
	}
	tagAmounts := map[string]int32{}
	for _, Knot := range knots {
		if len(Knot.Payload) > 0 {
			kpload := &apb.KnotPayload{}
			err := protojson.Unmarshal([]byte(Knot.Payload), kpload)
			if err != nil {
				return nil, status.Errorf(codes.Internal, "failed to unmarshal Knot payload: %v", err)
			}
			for _, tag := range kpload.Property.Tags {
				tagAmounts[tag]++
			}
		}
	}
	return &v1pb.ListKnotTagsResponse{
		TagAmounts: tagAmounts,
	}, nil
}

func (s *APIV1Service) RenameKnotTag(ctx context.Context, request *v1pb.RenameKnotTagRequest) (*emptypb.Empty, error) {
	user, err := s.GetCurrentUser(ctx)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get current user")
	}

	knotFind := &apb.FindKnot{
		CreatorId:       user.ID,
		Payload:         &apb.FindKnotPayload{TagSearch: []string{request.OldTag}},
		ExcludeComments: true,
	}

	if (request.Parent) != "knots/-" {
		knotID, err := utils.ExtractIDFromUrn(utils.KnotUrnPrefix, request.Parent)
		if err != nil {
			return nil, status.Errorf(codes.InvalidArgument, "invalid Knot urn: %v", err)
		}
		knotFind.Id = knotID
	}

	knots, _, err := s.Gds.Handler.ListKnots(ctx, knotFind)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to list knots")
	}

	for _, Knot := range knots {
		nodes, err := parser.Parse(tokenizer.Tokenize(Knot.Content))
		if err != nil {
			return nil, status.Errorf(codes.Internal, "failed to parse Knot: %v", err)
		}
		TraverseASTNodes(nodes, func(node ast.Node) {
			if tag, ok := node.(*ast.Tag); ok && tag.Content == request.OldTag {
				tag.Content = request.NewTag
			}
		})
		content := restore.Restore(nodes)

		property, err := getKnotPropertyFromContent(content)
		if err != nil {
			return nil, status.Errorf(codes.Internal, "failed to get Knot property: %v", err)
		}

		dpd := &apb.KnotPayload{
			Property: property,
		}

		if err := s.Gds.Handler.UpdateKnot(ctx, &apb.UpdateKnot{
			Id:      Knot.ID,
			Content: content,
			Payload: dpd,
		}); err != nil {
			return nil, status.Errorf(codes.Internal, "failed to update Knot: %v", err)
		}
	}

	return &emptypb.Empty{}, nil
}

func (s *APIV1Service) DeleteKnotTag(ctx context.Context, request *v1pb.DeleteKnotTagRequest) (*emptypb.Empty, error) {
	user, err := s.GetCurrentUser(ctx)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get current user")
	}

	knotFind := &apb.FindKnot{
		CreatorId:       user.ID,
		Payload:         &apb.FindKnotPayload{TagSearch: []string{request.Tag}},
		ExcludeContent:  true,
		ExcludeComments: true,
	}

	if (request.Parent) != "knots/-" {
		knotID, err := utils.ExtractIDFromUrn(utils.KnotUrnPrefix, request.Parent)
		if err != nil {
			return nil, status.Errorf(codes.InvalidArgument, "invalid Knot urn: %v", err)
		}
		knotFind.Id = knotID
	}

	knots, _, err := s.Gds.Handler.ListKnots(ctx, knotFind)

	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to list knots")
	}

	for _, Knot := range knots {
		if request.DeleteRelatedKnots {
			err := s.Gds.Handler.DeleteKnot(ctx, &apb.DeleteKnot{Id: Knot.ID})
			if err != nil {
				return nil, status.Errorf(codes.Internal, "failed to delete Knot")
			}
		} else {
			err := s.Gds.Handler.UpdateKnot(ctx, &apb.UpdateKnot{
				Id:        Knot.ID,
				RowStatus: cpb.RowStatus_ARCHIVED,
			})
			if err != nil {
				return nil, status.Errorf(codes.Internal, "failed to update Knot")
			}
		}
	}

	return &emptypb.Empty{}, nil
}

func (s *APIV1Service) GetKnotRecommendTopic(ctx context.Context, request *v1pb.GetKnotRecommendTopicsRequest) (*v1pb.GetKnotRecommendTopicsResponse, error) {
	currentUser, err := s.GetCurrentUser(ctx)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get current user")
	}

	id, err := utils.ExtractIDFromUrn(utils.KnotUrnPrefix, request.Urn)
	if err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "invalid Knot urn: %v", err)
	}

	Knot, err := s.Gds.Handler.GetKnot(ctx, &apb.FindKnot{
		Id: id,
	})

	if err != nil {
		return nil, err
	}

	if Knot == nil {
		return nil, status.Errorf(codes.NotFound, "Knot not found")
	}

	if Knot.TenantID != currentUser.TenantID {
		return nil, status.Errorf(codes.PermissionDenied, "permission denied")
	}

	rt, error := s.Gds.Handler.GetKnotRT(ctx, id)
	if error != nil {
		return nil, error
	}

	rts := []string{}
	if rt != nil && rt.Topics != "" {
		err = json.Unmarshal([]byte(rt.Topics), &rts)
		if err != nil {
			return nil, status.Errorf(codes.Internal, "failed to unmarshal Knot RT: %v", err)
		}
	}
	return &v1pb.GetKnotRecommendTopicsResponse{Topics: rts}, nil
}

func (s *APIV1Service) RegenKnotChat(ctx context.Context, request *v1pb.RegenKnotRequest) (*v1pb.Knot, error) {
	currentUser, err := s.GetCurrentUser(ctx)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get current user")
	}

	id, err := utils.ExtractIDFromUrn(utils.KnotUrnPrefix, request.Urn)
	if err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "invalid Knot urn: %v", err)
	}

	knot, err := s.Gds.Handler.GetKnot(ctx, &apb.FindKnot{
		Id: id,
	})

	if err != nil {
		return nil, err
	}

	if knot == nil {
		return nil, status.Errorf(codes.NotFound, "Knot not found")
	}

	if knot.CreatorID != currentUser.ID {
		return nil, status.Errorf(codes.PermissionDenied, "permission denied")
	}

	if request.DropChild {
		err = s.deleteKnotChild(ctx, knot.ID)
		if err != nil {
			return nil, errors.Wrap(err, "failed to delete Knot child for regen knot "+request.Urn)
		}
	}

	rootKnot := knot
	if knot.ParentID != 0 {
		rootKnot, err = s.Gds.Handler.GetKnot(ctx, &apb.FindKnot{
			Id: knot.ParentID,
		})
		if err != nil {
			return nil, errors.Wrap(err, "failed to get Knot for regen knot "+request.Urn)
		}
	}

	if s.WsManager != nil {
		if !s.WsManager.HaveHandler(rootKnot.UID) {
			historyKnots, _, err := s.Gds.Handler.ListKnots(ctx, &apb.FindKnot{
				ParentId: knot.ID})
			if err != nil {
				return nil, status.Errorf(codes.Internal, "failed to list Knots")
			}

			messages := &[]llm.RoleContent{}

			for _, historyKnot := range historyKnots {
				m := llm.RoleContent{
					Role:    "assistant",
					Content: historyKnot.Content,
				}
				if strings.HasPrefix(historyKnot.UID, "U_") {
					m.Role = "user"
				}
				*messages = append(*messages, m)

			}

			subject, err := s.Gds.Handler.GetSubjectById(ctx, rootKnot.SubjectID)
			if err != nil {
				return nil, status.Errorf(codes.Internal, "failed to get subject")
			}

			s.registerWebsocket(ctx, rootKnot, subject, knot.Content, messages)
		} else {
		}

	}

	return s.convertKnotFromDB(ctx, knot)
}

func (s *APIV1Service) deleteKnotChild(ctx context.Context, id int32) error {
	childrens, _, err := s.Gds.Handler.ListKnots(ctx, &apb.FindKnot{ParentId: id, ExcludeChildren: false})
	if err != nil {
		return status.Errorf(codes.Internal, "failed to list Knot comments")
	}
	for _, child := range childrens {
		if err := s.Gds.Handler.DeleteReaction(ctx, &apb.DeleteReaction{KnotId: child.ID}); err != nil {
			return status.Errorf(codes.Internal, "failed to delete Knot comment")
		}
		if _, err := s.DeleteKnot(ctx, &v1pb.DeleteKnotRequest{Urn: utils.BuildUrn(utils.KnotUrnPrefix, child.ID)}); err != nil {
			return status.Errorf(codes.Internal, "failed to delete Knot comment")
		}
	}
	return nil
}

func convertKnotPropertyFromDB(property *apb.KnotPayload_Property) *v1pb.KnotProperty {
	if property == nil {
		return nil
	}

	return &v1pb.KnotProperty{
		Tags:               property.Tags,
		HasLink:            property.HasLink,
		HasTaskList:        property.HasTaskList,
		HasCode:            property.HasCode,
		HasIncompleteTasks: property.HasIncompleteTasks,
	}
}

func (s *APIV1Service) buildKnotFindWithFilter(ctx context.Context, find *apb.FindKnot, filter string) error {
	if find == nil {
		find = &apb.FindKnot{}
	}
	if find.Payload == nil {
		find.Payload = &apb.FindKnotPayload{}
	}
	if filter != "" {
		filter, err := parseKnotFilter(filter)
		if err != nil {
			return status.Errorf(codes.InvalidArgument, "invalid filter: %v", err)
		}
		if len(filter.Keywords) > 0 {
			find.KeyWords = filter.Keywords
		}
		if len(filter.Visibilities) > 0 {
			find.Visibilities = filter.Visibilities
		}
		if filter.TagSearch != nil {
			if find.Payload == nil {
				find.Payload = &apb.FindKnotPayload{}
			}
			find.Payload.TagSearch = filter.TagSearch
		}
		if filter.OrderByPinned {
			find.OrderByPinned = filter.OrderByPinned
		}
		if filter.OrderByTimeAsc {
			find.OrderByTimeAsc = filter.OrderByTimeAsc
		}
		if filter.DisplayTimeAfter != nil {
			systemKnotRelationSetting, _ := s.GetSystemKnotRelatedSetting(ctx)

			if systemKnotRelationSetting.DisplayWithUpdateTime {
				find.UpdatedTsAfter = timestamppb.New(time.Unix(*filter.DisplayTimeAfter, 0))
			} else {
				find.CreatedTsAfter = timestamppb.New(time.Unix(*filter.DisplayTimeAfter, 0))
			}
		}
		if filter.DisplayTimeBefore != nil {
			systemKnotRelationSetting, _ := s.GetSystemKnotRelatedSetting(ctx)
			if err != nil {
				return status.Errorf(codes.Internal, "failed to get workspace Knot related setting")
			}
			if systemKnotRelationSetting.DisplayWithUpdateTime {
				find.UpdatedTsBefore = timestamppb.New(time.Unix(*filter.DisplayTimeBefore, 0))
			} else {
				find.CreatedTsBefore = timestamppb.New(time.Unix(*filter.DisplayTimeBefore, 0))
			}
		}
		if filter.Creator != nil {
			userID, err := utils.ExtractIDFromUrn(utils.UserUrnPrefix, *filter.Creator)
			if err != nil {
				return errors.Wrap(err, "invalid user name")
			}
			user, err := s.Gds.Handler.GetUser(ctx, &apb.FindUser{
				Id: userID,
			})
			if err != nil {
				return status.Errorf(codes.Internal, "failed to get user")
			}
			if user == nil {
				return status.Errorf(codes.NotFound, "user not found")
			}
			find.CreatorId = user.ID
		}

		if filter.RowStatus != nil {
			find.RowStatus = *filter.RowStatus
		}
		if filter.Random {
			find.Random = filter.Random
		}
		if filter.Limit != nil {
			find.Limit = *filter.Limit
		}
		if filter.IncludeComments {
			find.ExcludeComments = false
		}
		if filter.IncludeChildren {
			find.ExcludeChildren = false
		}

		if filter.HasLink {
			find.Payload.HasLink = true
		}
		if filter.HasTaskList {
			find.Payload.HasTaskList = true
		}
		if filter.HasCode {
			find.Payload.HasCode = true
		}
		if filter.HasIncompleteTasks {
			find.Payload.HasIncompleteTasks = true
		}

		if len(filter.Subject) > 0 {
			id, e := utils.ExtractIDFromUrn(utils.SubjectUrnPrefix, filter.Subject)
			if e == nil {
				find.SubjectId = id
			}
		}
	}

	user, err := s.GetCurrentUser(ctx)
	if err != nil {
		return status.Errorf(codes.Internal, "failed to get current user")
	}
	// If the user is not authenticated, only public knots are visible.
	if user == nil {
		// if filter == "" {
		// 	// If no filter is provided, return an error.
		// 	return status.Errorf(codes.InvalidArgument, "filter is required for unauthenticated user")
		// }

		find.Visibilities = []cpb.Visibility{cpb.Visibility_PUBLIC}
	} else if find.CreatorId != 0 && find.CreatorId != user.ID {
		// If the user is not the creator of the knot, only public knots are visible.
		find.Visibilities = []cpb.Visibility{cpb.Visibility_PUBLIC, cpb.Visibility_PROTECTED}
	}

	systemKnotRelationSetting, _ := s.GetSystemKnotRelatedSetting(ctx)
	if err != nil {
		return status.Errorf(codes.Internal, "failed to get workspace Knot related setting")
	}
	if systemKnotRelationSetting.DisplayWithUpdateTime {
		find.OrderByUpdatedTs = true
	}
	return nil
}

func (s *APIV1Service) getContentLengthLimit(ctx context.Context) (int, error) {
	systemKnotRelatedSetting, err := s.GetSystemKnotRelatedSetting(ctx)
	if err != nil {
		return 0, status.Errorf(codes.Internal, "failed to get workspace Knot related setting")
	}
	return int(systemKnotRelatedSetting.ContentLengthLimit), nil

}

// KnotFilterCELAttributes are the CEL attributes.
var KnotFilterCELAttributes = []cel.EnvOption{
	cel.Variable("keywords", cel.ListType(cel.StringType)),
	cel.Variable("visibilities", cel.ListType(cel.StringType)),
	cel.Variable("subject", cel.StringType),
	cel.Variable("tag_search", cel.ListType(cel.StringType)),
	cel.Variable("order_by_pinned", cel.BoolType),
	cel.Variable("order_by_time_asc", cel.BoolType),
	cel.Variable("display_time_before", cel.IntType),
	cel.Variable("display_time_after", cel.IntType),
	cel.Variable("creator", cel.StringType),
	cel.Variable("uid", cel.StringType),
	cel.Variable("row_status", cel.StringType),
	cel.Variable("random", cel.BoolType),
	cel.Variable("limit", cel.IntType),
	cel.Variable("include_comments", cel.BoolType),
	cel.Variable("include_children", cel.BoolType),
	cel.Variable("has_link", cel.BoolType),
	cel.Variable("has_task_list", cel.BoolType),
	cel.Variable("has_code", cel.BoolType),
	cel.Variable("has_incomplete_tasks", cel.BoolType),
}

type KnotFilter struct {
	Keywords           []string
	Visibilities       []cpb.Visibility
	TagSearch          []string
	OrderByPinned      bool
	OrderByTimeAsc     bool
	DisplayTimeBefore  *int64
	DisplayTimeAfter   *int64
	Creator            *string
	RowStatus          *cpb.RowStatus
	Random             bool
	Limit              *int32
	IncludeComments    bool
	IncludeChildren    bool
	HasLink            bool
	HasTaskList        bool
	HasCode            bool
	HasIncompleteTasks bool
	Subject            string
}

func parseKnotFilter(expression string) (*KnotFilter, error) {
	e, err := cel.NewEnv(KnotFilterCELAttributes...)
	if err != nil {
		return nil, err
	}
	ast, issues := e.Compile(expression)
	if issues != nil {
		return nil, errors.Errorf("found issue %v", issues)
	}
	filter := &KnotFilter{}
	expr, err := cel.AstToParsedExpr(ast)
	if err != nil {
		return nil, err
	}
	callExpr := expr.GetExpr().GetCallExpr()
	findKnotField(callExpr, filter)
	return filter, nil
}

func findKnotField(callExpr *expr.Expr_Call, filter *KnotFilter) {
	if len(callExpr.Args) == 2 {
		idExpr := callExpr.Args[0].GetIdentExpr()
		if idExpr != nil {
			if idExpr.Name == "keywords" {
				keywords := []string{}
				for _, expr := range callExpr.Args[1].GetListExpr().GetElements() {
					value := expr.GetConstExpr().GetStringValue()
					keywords = append(keywords, value)
				}
				filter.Keywords = keywords
			} else if idExpr.Name == "visibilities" {
				visibilities := []cpb.Visibility{}
				for _, expr := range callExpr.Args[1].GetListExpr().GetElements() {
					value := expr.GetConstExpr().GetStringValue()
					visibilities = append(visibilities, cpb.Visibility(cpb.Visibility_value[value]))
				}
				filter.Visibilities = visibilities
			} else if idExpr.Name == "tag_search" {
				tagSearch := []string{}
				for _, expr := range callExpr.Args[1].GetListExpr().GetElements() {
					value := expr.GetConstExpr().GetStringValue()
					tagSearch = append(tagSearch, value)
				}
				filter.TagSearch = tagSearch
			} else if idExpr.Name == "order_by_pinned" {
				value := callExpr.Args[1].GetConstExpr().GetBoolValue()
				filter.OrderByPinned = value
			} else if idExpr.Name == "order_by_time_asc" {
				value := callExpr.Args[1].GetConstExpr().GetBoolValue()
				filter.OrderByTimeAsc = value
			} else if idExpr.Name == "display_time_before" {
				displayTimeBefore := callExpr.Args[1].GetConstExpr().GetInt64Value()
				filter.DisplayTimeBefore = &displayTimeBefore
			} else if idExpr.Name == "display_time_after" {
				displayTimeAfter := callExpr.Args[1].GetConstExpr().GetInt64Value()
				filter.DisplayTimeAfter = &displayTimeAfter
			} else if idExpr.Name == "creator" {
				creator := callExpr.Args[1].GetConstExpr().GetStringValue()
				filter.Creator = &creator
			} else if idExpr.Name == "row_status" {
				v := callExpr.Args[1].GetConstExpr().GetStringValue()
				rowStatus := cpb.RowStatus(cpb.RowStatus_value[v])
				filter.RowStatus = &rowStatus
			} else if idExpr.Name == "random" {
				value := callExpr.Args[1].GetConstExpr().GetBoolValue()
				filter.Random = value
			} else if idExpr.Name == "limit" {
				limit := int32(callExpr.Args[1].GetConstExpr().GetInt64Value())
				filter.Limit = &limit
			} else if idExpr.Name == "include_comments" {
				value := callExpr.Args[1].GetConstExpr().GetBoolValue()
				filter.IncludeComments = value
			} else if idExpr.Name == "include_children" {
				value := callExpr.Args[1].GetConstExpr().GetBoolValue()
				filter.IncludeChildren = value
			} else if idExpr.Name == "has_link" {
				value := callExpr.Args[1].GetConstExpr().GetBoolValue()
				filter.HasLink = value
			} else if idExpr.Name == "has_task_list" {
				value := callExpr.Args[1].GetConstExpr().GetBoolValue()
				filter.HasTaskList = value
			} else if idExpr.Name == "has_code" {
				value := callExpr.Args[1].GetConstExpr().GetBoolValue()
				filter.HasCode = value
			} else if idExpr.Name == "has_incomplete_tasks" {
				value := callExpr.Args[1].GetConstExpr().GetBoolValue()
				filter.HasIncompleteTasks = value
			} else if idExpr.Name == "subject" {
				v := callExpr.Args[1].GetConstExpr().GetStringValue()
				filter.Subject = v
			}
			return
		}
	}
	for _, arg := range callExpr.Args {
		callExpr := arg.GetCallExpr()
		if callExpr != nil {
			findKnotField(callExpr, filter)
		}
	}
}

func getKnotPropertyFromContent(content string) (*apb.KnotPayload_Property, error) {
	nodes, err := parser.Parse(tokenizer.Tokenize(content))
	if err != nil {
		return nil, errors.Wrap(err, "failed to parse content")
	}

	property := &apb.KnotPayload_Property{}
	TraverseASTNodes(nodes, func(node ast.Node) {
		switch n := node.(type) {
		case *ast.Tag:
			tag := n.Content
			if !slices.Contains(property.Tags, tag) {
				property.Tags = append(property.Tags, tag)
			}
		case *ast.Link, *ast.AutoLink:
			property.HasLink = true
		case *ast.TaskList:
			property.HasTaskList = true
			if !n.Complete {
				property.HasIncompleteTasks = true
			}
		case *ast.Code, *ast.CodeBlock:
			property.HasCode = true
		}
	})
	return property, nil
}

func TraverseASTNodes(nodes []ast.Node, fn func(ast.Node)) {
	for _, node := range nodes {
		fn(node)
		switch n := node.(type) {
		case *ast.Paragraph:
			TraverseASTNodes(n.Children, fn)
		case *ast.Heading:
			TraverseASTNodes(n.Children, fn)
		case *ast.Blockquote:
			TraverseASTNodes(n.Children, fn)
		case *ast.OrderedList:
			TraverseASTNodes(n.Children, fn)
		case *ast.UnorderedList:
			TraverseASTNodes(n.Children, fn)
		case *ast.TaskList:
			TraverseASTNodes(n.Children, fn)
		case *ast.Bold:
			TraverseASTNodes(n.Children, fn)
		}
	}
}

// DispatchKnotCreatedWebhook dispatches webhook when Knot is created.
func (s *APIV1Service) DispatchKnotCreatedWebhook(ctx context.Context, Knot *v1pb.Knot) error {
	return s.dispatchKnotRelatedWebhook(ctx, Knot, "knots.Knot.created")
}

// DispatchKnotUpdatedWebhook dispatches webhook when Knot is updated.
func (s *APIV1Service) DispatchKnotUpdatedWebhook(ctx context.Context, Knot *v1pb.Knot) error {
	return s.dispatchKnotRelatedWebhook(ctx, Knot, "knots.Knot.updated")
}

// DispatchKnotDeletedWebhook dispatches webhook when Knot is deleted.
func (s *APIV1Service) DispatchKnotDeletedWebhook(ctx context.Context, Knot *v1pb.Knot) error {
	return s.dispatchKnotRelatedWebhook(ctx, Knot, "knots.Knot.deleted")
}

func (s *APIV1Service) dispatchKnotRelatedWebhook(ctx context.Context, Knot *v1pb.Knot, activityType string) error {
	creatorID, err := utils.ExtractIDFromUrn(utils.UserUrnPrefix, Knot.Creator)

	if err != nil {
		return status.Errorf(codes.InvalidArgument, "invalid Knot creator")
	}

	webhooks, err := s.Gds.Handler.ListWebhooks(ctx, &apb.FindWebhook{
		CreatorId: creatorID,
	})

	if err != nil {
		return err
	}
	for _, hook := range webhooks {
		payload, err := convertKnotToWebhookPayload(Knot)
		if err != nil {
			return errors.Wrap(err, "failed to convert Knot to webhook payload")
		}
		payload.ActivityType = activityType
		payload.Url = hook.URL
		if err := webhook.Post(payload); err != nil {
			return errors.Wrap(err, "failed to post webhook")
		}
	}
	return nil
}

func convertKnotToWebhookPayload(Knot *v1pb.Knot) (*v1pb.WebhookRequestPayload, error) {
	creatorID, err := utils.ExtractIDFromUrn(utils.UserUrnPrefix, Knot.Creator)
	if err != nil {
		return nil, errors.Wrap(err, "invalid Knot creator")
	}
	return &v1pb.WebhookRequestPayload{
		CreatorId:  creatorID,
		CreateTime: timestamppb.New(time.Now()),
		Knot:       Knot,
	}, nil
}

func getKnotContentSnippet(content string) (string, error) {
	nodes, err := parser.Parse(tokenizer.Tokenize(content))
	if err != nil {
		return "", errors.Wrap(err, "failed to parse content")
	}

	plainText := renderer.NewStringRenderer().Render(nodes)
	if len(plainText) > 100 {
		return substring(plainText, 100) + "...", nil
	}
	return plainText, nil
}

func substring(s string, length int) string {
	if length <= 0 {
		return ""
	}

	runeCount := 0
	byteIndex := 0
	for byteIndex < len(s) {
		_, size := utf8.DecodeRuneInString(s[byteIndex:])
		byteIndex += size
		runeCount++
		if runeCount == length {
			break
		}
	}

	return s[:byteIndex]
}
